from PySide6.QtCore import QObject, Property, Signal, QPointF
from PySide6.QtGui import QColor

from PySide6.QtQuick import (QQuickItem, QSGFlatColorMaterial,
                             QSGGeometry, QSGGeometryNode, QSGNode)
from PySide6.QtQml import QmlElement

QML_IMPORT_NAME = "BppTableModel"
QML_IMPORT_MAJOR_VERSION = 1

@QmlElement
class BTModel(QObject):
    def __init__(self,parent=None):
        super().__init__(parent)
        self.dbRef=emptyDbRef
        self.dataChangedALS=False
        self.columnsChangedALS=False
        self.sortedChangedALS=False
        self.highlightRow=-1
        self.lastHighlightRow=-1
        self.hasMultiselection=False
        self.onlyHighlightRole=CustomRoles.roleHighlight #onlyHighlightRole{CustomRoles.roleHighlight}


    def rowCount(self,index):
        self.dataVal.size()

    def columnCount(self,index):
        self.columnsDef.size();

    def data(self,cellIndex, role):

        cdef = self.getColumnDef(cellIndex.column() )

        if role==Qt.DisplayRole:
            return self.getDataDisplayRole(cellIndex)
        elif role==roleDataType:
            return self.cdef.type;
        elif role==roleView:
            return self.cdef.view;
        elif role== roleCommand:
            return self.cdef.command
        elif role==roleHighlight:
            if( self.highlightRows.count( cellIndex.row() ) ):
                return True;
            return False;
        elif role== roleVisible:
            return self.cdef.visible;
        elif role== roleRef1:
            reference = self.cdef.reference1;
            if(reference >= 0):
                return self.getDataDisplayRole( QModelIndex( index(cellIndex.row(), reference) ) )
            else:
                return ""

        return None

    #https:#doc.qt.io/qt-5/qt.html#ItemDataRole-enum
    def roleNames(self):
        # return {
        #     {Qt.DisplayRole, "display"},
        #     {roleDataType, "dataType"},
        #     {roleView, "view"},
        #     {roleCommand, "command"},
        #     {roleHighlight, "highlight"},
        #     {roleVisible, "visible"},
        #     {roleRef1, "ref1"}
        # };
        pass
    def sortData(self):

        if(self.dataChangedALS or self.columnsChangedALS or self.sortedChangedALS):
            self.setHighlightRow(-1, 0);

            doSort=True
            if(dataVal.isEmpty()): 
                doSort = False;

            if(doSort):
                if(sortColumns.isEmpty()): # no sort
                    doSort = false;
                    if(not dataIndex.isEmpty()) :
                        beginResetModel();
                        dataIndex.clear();
                        endResetModel();

            if(doSort):
                dataIndex.fill(0, dataVal.size());
                # std.iota(dataIndex.begin(), dataIndex.end(), 0);

                sortIndexes=[]
                for sortCol in  sortColumns:
                    orderAsc=True
                    iCol = sortCol;
                    if(iCol < 0):
                        orderAsc = False;
                        iCol = -iCol;

                    #-1 because sortColumns value starts from 1 for the first column
                    sortIndexes.push_back( QPair<int,bool>(iCol - 1, orderAsc) );

                self.beginResetModel();
                self.sort(dataIndex.begin(), dataIndex.end(), self.sortFunc)
                self.endResetModel();
            dataChangedALS = false;
            columnsChangedALS = false;
            sortedChangedALS = false;

    def sortFunc(self,indexA,  indexB):

        # recordA = self.dataVal[indexA];
        # recordB = self.dataVal[indexB];
        # for sortCol in  sortIndexes:
        #     orderAsc=sortCol.second
        #     iCol=sortCol.first

        #     valA = recordA[iCol];
        #     valB = recordB[iCol];

        #     if(valA.isNull() && !valB.isNull())
        #         return orderAsc;
        #     if(valB.isNull() && !valA.isNull())
        #         return !orderAsc;

        #     switch (getColumnDef( iCol ).type) {
        #     case TableColumn.Str:
        #         if(valA.toString().compare(valB.toString(), Qt.CaseInsensitive) == 0):
        #             continue;
        #         break;
        #     case TableColumn.Dbl:
        #         if( fabs(valA.toDouble() - valB.toDouble()) < double(FLT_EPSILON) )
        #             continue;
        #         break;
        #     case TableColumn.Int:
        #         if( valA.toInt() == valB.toInt() )
        #             continue;
        #         break;
        #     case TableColumn.Date:
        #         if( valA.toDate() == valB.toDate() )
        #             continue;
        #         break;
        #     case TableColumn.DateTime:
        #         if( valA.toDateTime() == valB.toDateTime() )
        #             continue;
        #         break;

        #     if(orderAsc):
        #         switch (getColumnDef( iCol ).type) {
        #         case TableColumn.Str:
        #             return valA.toString().compare(valB.toString(), Qt.CaseInsensitive) < 0;
        #         case TableColumn.Dbl:
        #             return valA.toDouble() < valB.toDouble();
        #         case TableColumn.Int:
        #             return valA.toInt() < valB.toInt();
        #         case TableColumn.Date:
        #             return valA.toDate() < valB.toDate();
        #         case TableColumn.DateTime:
        #             return valA.toDateTime() < valB.toDateTime();
        #     else:
        #         switch (getColumnDef( iCol ).type) {
        #         case TableColumn.Str:
        #             return valA.toString().compare(valB.toString(), Qt.CaseInsensitive) > 0;
        #         case TableColumn.Dbl:
        #             return valA.toDouble() > valB.toDouble();
        #         case TableColumn.Int:
        #             return valA.toInt() > valB.toInt();
        #         case TableColumn.Date:
        #             return valA.toDate() > valB.toDate();
        #         case TableColumn.DateTime:
        #             return valA.toDateTime() > valB.toDateTime();
        return False;
    def beginReset(self, appendMode):
        self.beginResetModel();
        if(not appendMode):
            self.dataVal=[]#.clear();
            #dataIndex.clear();
        self.dataChangedALS = True;

    def endReset(self):
        self.endResetModel()
        self.sortData()

    def addFromQuery(self,theSqlQuery, parameters):
        # if(!dbRef->getDb()) {
        #     qDebug() << "No database";
        #     return false;
        # }

        # QSqlQuery query(*dbRef->getDb());
        # query.setForwardOnly(true);

        # if(!query.prepare(theSqlQuery)){
        #     qDebug() << "Error querying the db" << query.lastError().text();
        #     return false;
        # }

        # if(!parameters.isEmpty()) {
        #     for(auto& curParam: parameters) {
        #         query.addBindValue(curParam);
        #     }
        # }

        # bool allOk(true);
        # if(query.exec()) {

        #     beginReset(false);

        #     if(!addFrontRecords.isEmpty())
        #         allOk = addFromList(addFrontRecords, false);

        #     dataVal.reserve(100);
        #     int nCols = columnsDef.size();
        #     while(query.next() && allOk) {
        #         dataVal.push_back(QVector<QVariant>());
        #         QVector<QVariant>& curRow = dataVal.last();
        #         curRow.reserve( nCols );

        #         for(int iCol = 0; iCol < nCols; iCol++) {
        #             #if(getColumnDef( iCol ).reference1 < 0)
        #                 appendDataVariant(curRow, query.value(iCol), getColumnDef( iCol ).type, DataDialect.Sqlite);
        #             #else
        #             #    appendDataVariant(curRow, emptyVInt, getColumnDef( iCol ).type, DataDialect.JsonISO);
        #         }
        #     }
        #     dataVal.shrink_to_fit();

        #     endReset();
        # }

        # if(query.lastError().isValid())
        # {
        #     allOk = false;
        #     qDebug() << "Error querying the db" << query.lastError().text();
        # }
        # query.finish();

        # return allOk;
        pass

    def addFromList(self,values, resetList):
        pass
        # bool allOk(true);
        # #if(resetList)
        #     beginReset(!resetList);

        # for(auto& curRecord: values){
        #     const QVariantMap& curValues = curRecord.toMap();

        #     dataVal.push_back(QVector<QVariant>());
        #     QVector<QVariant>& curRow = dataVal.last();

        #     for(int iCol = 0; iCol < columnsDef.size(); iCol++) {
        #         if(!curValues.contains(getColumnDef( iCol ).role))
        #             appendDataVariant(curRow, emptyVInt, getColumnDef( iCol ).type, DataDialect.JsonISO);
        #         else
        #             appendDataVariant(curRow, curValues[getColumnDef( iCol ).role], getColumnDef( iCol ).type, DataDialect.JsonISO);
        #     }
        # }

        # #if(resetList)
        #     endReset();
        # return allOk;

    def setFrontRecords(self,values):
        self.addFrontRecords = values;

    def countFromQuery(self,theSqlQuery, parameters):
        if(not dbRef.getDb()):
            print("No database")
            return False;

        query=QSqlQuery(dbRef.getDb());
        query.setForwardOnly(True);

        if(not query.prepare(theSqlQuery)):
            logging.info( ["Error querying the db" , query.lastError().text()])
            return 0;

        if(not parameters.isEmpty()):
            for curParam in  parameters:
                query.addBindValue(curParam)

        numRecords=0

        allOk=True
        if(query.exec()):
            if(query.next()):
                numRecords = int(query.value(0))
        if(query.lastError().isValid()):
            allOk = False;
            logging.info(["Error querying the db" , query.lastError().text()])
        query.finish();

        if(allOk):
            return numRecords;

        return 0;

    def addRecord(self, theData):
        numColumns=len(columnsDef)

        self.dataChangedALS = True;
        self.dataVal.append(QVector<QVariant>());
        curRow = self.dataVal.last()
        iCol=0;
#         for  theValue in theData:
#             #if(getColumnDef( iCol ).reference1 < 0)
#                 appendDataVariant(curRow, theValue, getColumnDef( iCol ).type, DataDialect.JsonISO);
# #            else
# #                appendDataVariant(curRow, emptyVInt, getColumnDef( iCol ).type, DataDialect.JsonISO);
#             iCol++;
#         }

        while(iCol < numColumns):
            self.appendDataVariant(curRow, emptyVInt, getColumnDef( iCol ).type, DataDialect.JsonISO);
            iCol+=1

    def updateLayout(self):
        self.layoutChanged.emit();
        self.sortData();


    def setDbRef(self,value):
        self.dbRef = value;

    def endUpdateColumns(self):
        self.calcReferenceColumns();
        self.calcSortColumns();
        self.updateLayout();

    def canHideColumns(self):
        return True;

    def copyRowToClipboard(self, row):
        text = self.getRowString(row)
        clipboard = QGuiApplication.clipboard();

        clipboard.setText(text, QClipboard.Clipboard);
        if (clipboard.supportsSelection()):
            clipboard.setText(text, QClipboard.Selection);

        return True;

    def getRowString(self,row):
        toRet=""

        toRet+="{"

        iCol=0
        appended=0;
        for col in self.columnsDef:
            if(col.command == 0):
                if(appended):
                    toRet.append( ", " );
                if(col.title is None):
                    toRet.append( "\"column_"+str(iCol + 1)+"\": ")
                else:
                    toRet.append( "\"+col.title+\": ")
                if(col.type == TableColumn.Str or col.type == TableColumn.Date or col.type == TableColumn.DateTime):
                    toRet.append( "\"" );
                    toRet.append( self.data( QModelIndex( index(row, iCol) ), Qt.DisplayRole).toString() );
                    toRet.append( "\"" );
                else:
                    toRet.append( self.data( QModelIndex( index(row, iCol) ), Qt.DisplayRole).toString() );
                appended+=1;
            iCol+=1;
        toRet.append("}");

        return toRet;

    def getRowObject(self, row):
        toRet={}
        iCol = 0;
        for  col in self.columnsDef:
            toRet[col.role] = self.getDataDisplayRole( QModelIndex( index(row, iCol) ) )
            iCol+=1
        return toRet;

    def setCellValue(self, rowNum,  columnNum, data):

        if(rowNum < 0) or (rowNum >= len(self.dataVal)):
            return False;
        if(columnNum < 0 or columnNum >= len(self.columnsDef)):
            return False;

        whatRow = rowNum;
        if(not dataIndex is None):
            whatRow = dataIndex[whatRow];

        if( columnNum + 1 in sortColumns or -(columnNum + 1) in sortColumns):
            #data need to be resorted
            self.beginReset(True);
            self.dataVal[whatRow][columnNum] = data;
            self.endReset();
        else:
            dataVal[whatRow][columnNum] = data;
            self.dataChanged.emit( QModelIndex( index(rowNum, columnNum) ),  QModelIndex( index(rowNum, columnNum) ), {Qt.DisplayRole});
        return True;

    def getColWidth(self, columnId):
        theW=0
        if(columnId<0):
            theW = 100;
        elif(self.columnsDef[columnId].visible):
            theW = self.columnsDef[columnId].width;

        #qDebug() << columnId << theW;
        return theW;

    def clearColumnsDef(self):
        # for  col in self.columnsDef:
        #     delete col;
        # }
        self.columnsDef=[]
        self.columnsChangedALS = True;

    def addColumnDef(self):
        newCol=len(self.columnsDef)
        self.columnsDef.append( TableColumn() )
        self.columnsChangedALS = True;
        return newCol;

    def setColumnDef(self, columnId,  withDefaults, colDef):
        self.columnsDef[columnId].modify(colDef, withDefaults)

    def getColumnDef(self, columnId):
        return self.columnsDef[columnId];

    def getColumnId(self,columnRole):
        columnNum = -1;
        for  iCol in range(len(columnsDef)):
            if (columnRole == columnsDef[iCol].role):
                columnNum = iCol;
                break;
        return columnNum;

    def appendDataVariant(self,record, theValue, columnType, dia):
        pass
        # if(theValue.isNull())
        #     switch(columnType){
        #     case TableColumn.Str:
        #         record.push_back( emptyVString );
        #         break;
        #     case TableColumn.Dbl:
        #         record.push_back( emptyVDouble );
        #         break;
        #     case TableColumn.Int:
        #         record.push_back( emptyVInt );
        #         break;
        #     case TableColumn.Date:
        #         record.push_back( emptyVDate );
        #         break;
        #     case TableColumn.DateTime:
        #         record.push_back( emptyVDateTime );
        #         break;
        #     }
        # else {
        #     switch(columnType){
        #     case TableColumn.Str:
        #         record.push_back( theValue.toString() );
        #         break;
        #     case TableColumn.Dbl:
        #         record.push_back( theValue.toDouble() );
        #         break;
        #     case TableColumn.Int:
        #         record.push_back( theValue.toInt() );
        #         break;
        #     case TableColumn.Date:
        #         if(dia == DataDialect.Sqlite)
        #             record.push_back( QDate.fromString(theValue.toString(),"yyyy-MM-dd") );
        #         else if(dia == DataDialect.JsonISO)
        #             record.push_back( QDate.fromString(theValue.toString(), Qt.ISODate) );
        #         else
        #             record.push_back( theValue.toDate() );
        #         break;
        #     case TableColumn.DateTime:
        #         if(dia == DataDialect.Sqlite)
        #             record.push_back( QDateTime.fromString(theValue.toString(),"yyyy-MM-dd HH:mm:ss") );
        #         else if(dia == DataDialect.JsonISO)
        #             record.push_back( QDateTime.fromString(theValue.toString(), Qt.ISODate) );
        #         else
        #             record.push_back( theValue.toDateTime() );
        #         break;
        #     }
        # }

    def calcSortColumns(self):
        newSortColumns=[]
        for i in range(len(self.columnsDef)):#int i = 0; i < columnsDef.size(); i++){
            sortVal = self.getColumnDef(i).sortFlag;
            if(sortVal == 1):
                newSortColumns.push_back(i+1);
            if(sortVal == 2):
                newSortColumns.push_back(-(i+1));
        if(newSortColumns != sortColumns):
            sortColumns = newSortColumns;
            sortedChangedALS = True;

    def calcReferenceColumns(self):
        for iColumn in range(len(self.columnsDef)):
            currentColumn = self.columnsDef[iColumn];
            if(currentColumn.dataRef1.isEmpty()):
                currentColumn.reference1 = -1;
            else:
                currentColumn.reference1 = -1;
                iSeek=0
                # for(int iSeek = 0; ; iSeek++):
                while(iSeek < len(self.columnsDef) and currentColumn.reference1 == -1):
                    seekColumn = self.columnsDef[iSeek]
                    if(not seekColumn.role is None and currentColumn.dataRef1.compare(seekColumn.role) == 0):
                        currentColumn.reference1 = iSeek;
                    iSeek+=1

    def getDataDisplayRole(self,index):
        whatRow = index.row();
        if(not dataIndex.isEmpty()):
            whatRow = dataIndex[whatRow];

        colVal = self.dataVal[whatRow][index.column()];
        type1=self.getColumnDef( index.column() ).type
        if type1== TableColumn.Str:
            if(colVal.isNull()):
                return QVariant(QVariant.String);
            else:                
                return colVal.toString();
        elif type1==TableColumn.Dbl:
            if(colVal.isNull()): 
                return QVariant(QVariant.Double);
            else:                
                return colVal.toDouble();
        elif type1==TableColumn.Int:
            if(colVal.isNull()): 
                return QVariant(QVariant.Int);
            else:                
                return colVal.toInt();
        elif type1==TableColumn.Date:
            if(colVal.isNull()): 
                return QVariant(QVariant.Date);
            else:                
                return colVal.toDate();
        elif type1==TableColumn.DateTime:
            if(colVal.isNull()): 
                return QVariant(QVariant.DateTime);
            else:                
                return colVal.toDateTime();
        return colVal.toString();
    
    def getMultiselectionMobileMode(self):
        return multiselectionMobileMode;

    def setMultiselectionMobileMode(self, value):
        if(multiselectionMobileMode != value):
            multiselectionMobileMode = value;
            self.multiselectionMobileModeChanged.emit();

    def getHasMultiselection(self):
        return self.hasMultiselection;

    def setHasMultiselection(self,value):
        if(value != hasMultiselection):
            self.hasMultiselection = value;
            if(not hasMultiselection and highlightRows.size()>1):
                self.setHighlightRow(-1, 0);
            self.hasMultiselectionChanged.emit()

    def getHighlightRow(self):
        return self.highlightRow;

    def isHighlightRow(self, rowNum):
        return self.highlightRows.count(rowNum)>0

    def countHighlightRows(self):
        return int(self.highlightRows.size());

    def getHighlightRows(self):
        toRet=[]
        for curRow in  highlightRows:
            toRet.push_back(curRow);
        return toRet;

    def dataNeedSort(self):
        self.columnsChangedALS = True;

    def setHighlightRow(self, rowNum,  modifiers):
        pressedCtrl = modifiers & Qt.ControlModifier;
        pressedShift = modifiers & Qt.ShiftModifier;

        fireRowChanged=False
        minRow=-1 
        maxRow=-1

        if(multiselectionMobileMode):
            pressedCtrl = True;

        if(highlightRows.count(rowNum)):
            if(not hasMultiselection or pressedCtrl or highlightRows.size() == 1):
                #deselect it
                highlightRows.erase(rowNum);
                if(highlightRows.empty()):
                    highlightRow = -1;
                else:
                    highlightRow = self.highlightRows.begin();
                fireRowChanged = true;
                minRow = rowNum; maxRow = rowNum;
                lastHighlightRow = -1;
            else:
                if(hasMultiselection):
                    minRow = self.highlightRows.begin();
                    maxRow = self.highlightRows.rbegin();
                    highlightRows.clear();
                    highlightRows.insert(rowNum);
                    highlightRow = rowNum;
                    lastHighlightRow = rowNum;
                    fireRowChanged = true;
        else:
            if(rowNum == -1):
                #select none
                if(not highlightRows.empty()):
                    #clear selection
                    minRow = self.highlightRows.begin();
                    maxRow = self.highlightRows.rbegin();
                    highlightRows.clear();
                    highlightRow = -1;
                    lastHighlightRow = -1;
                    fireRowChanged = true;
            elif(rowNum == -2):
                #select all
                for irow in range(len(self.dataVal)): #.size(); irow++):
                    highlightRows.insert(irow);
                fireRowChanged = true;
                highlightRow = dataVal.size()-1;
                lastHighlightRow = dataVal.size()-1;
                if(not highlightRows.empty()):
                    minRow = self.highlightRows.begin();
                    maxRow = self.highlightRows.rbegin();
            else :
                if(not hasMultiselection or (not pressedCtrl and not pressedShift)):
                    if(not highlightRows.empty()):
                        minRow = self.highlightRows.begin();
                        maxRow = self.highlightRows.rbegin();
                        highlightRows.clear();
                if(not hasMultiselection or not pressedShift or lastHighlightRow == -1):
                    self.highlightRows.insert(rowNum);
                else:
                    fromRow = lastHighlightRow;
                    toRow = rowNum;
                    if(fromRow > toRow):
                        tmp=fromRow
                        fromRow= toRow
                        toRow=tmp
                    minRow = fromRow;
                    maxRow = toRow;
                    while(fromRow <= toRow):
                        self.highlightRows.insert(fromRow);
                        fromRow+=1

                highlightRow = rowNum;
                lastHighlightRow = rowNum;
                fireRowChanged = true;
                if(minRow < 0 or rowNum < minRow): 
                    minRow = rowNum;
                if(maxRow < 0 or rowNum > maxRow): 
                    maxRow = rowNum;

        if(fireRowChanged):
            self.highlightRowChanged.emit();
            if(minRow != -1):
                #qDebug() << rowNum << minRow << maxRow;
                self.dataChanged.emit(QModelIndex(index(minRow, 0)), QModelIndex(index(maxRow, columnsDef.size() - 1)), onlyHighlightRole);


    def setHighlightRows(self, emptySel, rowsIdx):

        fireRowChanged=False
        minRow=-1
        maxRow=-1

        if(emptySel):
            if(not len(highlightRows)==0):
                #clear selection
                minRow = self.highlightRows.begin();
                maxRow = self.highlightRows.rbegin();
                highlightRows.clear();
                highlightRow = -1;
                lastHighlightRow = -1;
                fireRowChanged = true;
        if(not len(rowsIdx)==0):
            for irow in range(len(rowsIdx)):
                highlightRows.insert(rowsIdx[irow]);
            fireRowChanged = True;
            highlightRow = rowsIdx[len(rowsIdx)-1];
            lastHighlightRow = highlightRow;
            if(not len(self.highlightRows)==0):
                if(minRow < 0 or self.highlightRows.begin() < minRow):
                    minRow = self.highlightRows.begin();
                if(maxRow < 0 or self.highlightRows.rbegin() > maxRow):
                    maxRow = self.highlightRows.rbegin();

        if(fireRowChanged):
            self.highlightRowChanged.emit()
            if(minRow != -1):
                #qDebug() << rowNum << minRow << maxRow;
                self.dataChanged.emit(QModelIndex(index(minRow, 0)), QModelIndex(index(maxRow, columnsDef.size() - 1)), onlyHighlightRole);

